package com.gitee.wsl.struct.grammar.parser.token

import com.gitee.wsl.ext.base.toInt
import com.gitee.wsl.struct.grammar.parser.combinator.map
import com.gitee.wsl.struct.grammar.parser.combinator.oneOrMore
import com.gitee.wsl.struct.grammar.parser.combinator.zeroOrMore

/**
 * Parses a char if it satisfies a given predicate.
 * @param predicate returns whether the parsing is successful.
 * @return a parser that parses a character for a predicate.
 */
fun Token.Companion.sat(predicate: (Char) -> Boolean): Token = Token { string, index->
    if(index < string.length) predicate(string[index]).toInt() else 0
}

fun Token.Companion.takeWhile(predicate: (Char) -> Boolean) = sat { !predicate(it) }.zeroOrMore().map { ComposeTokenMatch(it) }

val Token.Companion.letter: Token
    get() = sat { it.isLetter() }

val Token.Companion.digit: Token
    get() = sat { it.isDigit() }

val Token.Companion.digits get() = digit.oneOrMore().map { ComposeTokenMatch(it) }

val Token.Companion.int  get() =  regex("\\d+")

fun Token.Companion.char(c: Char) = sat { it == c }

/**
 * Parses whitespace ' ' (space), '\t' tab or '\n'.
 * This operation never fails. For empty text, simple returns empty list of chars.
 */
val Token.Companion.whitespace get() = (sat { it.isWhitespace() }).oneOrMore().map { ComposeTokenMatch(it) }

val Token.Companion.num get()  = token(firstChars = "+-0123456789.") { it, at ->
    var index = at
    val maybeSign = it[index]
    val sign = maybeSign == '+' || maybeSign == '-'
    if (sign) index++

    val length = it.length
    while (index < length && it[index].isDigit()) {
        index++
    }

    if (index < length && it[index] == '.') { // decimal
        index++
        while (index < length && it[index].isDigit()) {
            index++
        }
    }
    if (index == at || (index == at + 1 && sign)) return@token 0
    index - at
}

val Token.Companion.string get() = token(firstChars = "\"") { it, at ->
    var index = at
    if (it[index++] != '"') return@token 0
    val length = it.length
    while (index < length && it[index] != '"') {
        if (it[index] == '\\') { // quote
            index++
        }
        index++
    }
    if (index == length) return@token 0 // unclosed string
    index + 1 - at
}

fun Token.Companion.hexString(maxLength:Int=Int.MAX_VALUE) = token(firstChars = "0") { it, at ->
    var index = at
    val nextChar = it[index++]
    if (nextChar == 'x' || nextChar == 'X') return@token 0

    val length = it.length
    while (index < length && it[index].isDigit()) {
        index++
    }

    if (index == at || (index == at + 1) || (index-at)>maxLength) return@token 0
    index - at
}

